package org.jugile.daims;

/*

Copyright (C) 2011-2011 Jukka Rahkonen  email: jukka.rahkonen@iki.fi

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

*/


import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.log4j.Logger;
import org.jugile.util.Buffer;
import org.jugile.util.DBConnection;
import org.jugile.util.Jugile;

/**
 * <i>"this is verse"</i> <b>()</b>
 * 
 * <br/>==========<br/>
 *
 * here is doc
 *  
 * @author jukka.rahkonen@iki.fi
 *
 */
public class BoMap<E extends Bo> extends Jugile implements Iterable<E> {
	static Logger log = Logger.getLogger(BoMap.class);

	private Map<Long,E> items = new HashMap<Long,E>(2);
	
	private final DomainData dd;
	private final Class<E> clazz;
	protected Class<E> getClazz() { return clazz; }

	public BoMap(Class<E> cl, DomainData dd) {
		clazz = cl;
		this.dd = dd;
	}

	protected void add(E o) {
		// add new created object
		if (o == null) return;
		if (!o.isNew() && !o.isOrigin()) fail("only new or origin objects can be added");
		items.put(o.id(),o);
		//log.debug("added " + o + "to " + this);
	}
	
	
	protected E getCopy(long id) {
		E o = items.get(id);
		if (o == null) return null;
		E res = (E)o.copy(bi());
		//log.debug("created copy: " + res);
		return res;
	}
	
	protected E getOrCreateOld(long id) {
		if (id <= 0) return null;
		E o = items.get(id);
		if (o == null) {
			o = (E)Bo.createOld(clazz,id);
			items.put(id,o);
			//log.debug("created old " + o + "to " + this);
		}
		return o;
	}

	
	public int size() { return items.size(); }
	public void remove(E o) { 
		if (o != null) items.remove(o.id());
	}	
	public E remove(long id) {
		//log.debug("remove: " + id);
		return items.remove(id);
	}

	public boolean contains(E o) {
		if (o == null) return false;
		return contains(o.id());
	}
	public boolean contains(long id) {
		return items.containsKey(id);
	}
	
	private Iterator<E> iterator;
	public Iterator<E> iterator() {
		iterator = new BoMapIterator(this);
		return iterator;
	}
	public void remove() {
		iterator.remove();
	}

	
	/**
	 * Wrapper for underlying collection (eg. Trove uses custom iterators).
	 */
	private class BoMapIterator implements Iterator<E> {
		Iterator<E> i = null;
		public BoMapIterator(BoMap<E> bm) { i = bm.items.values().iterator(); }
		public boolean hasNext() { return i.hasNext(); }
		public E next() { return i.next(); } 
		public void remove() { i.remove(); }
	}

	protected E _first() {
		iterator();
		if (iterator.hasNext()) return iterator.next();
		return null;
	}
	
	protected void parse(List<String> headers, List<String> cols) {
		E o = getOrCreateOld(parseLongSafe(cols.get(0)));
		// set private fields
		try { o.parse(dd,headers,cols); } catch (Exception e) { fail(e); }
	}
	
	// for testing
	protected E getOriginal(long id) { return items.get(id); }
	
	public String csv() {
		if (items.size() == 0) return ""; // empty set
		Buffer buf = new Buffer();
		buf.ln(_first()._headerRow());
		if (items.size() > 0) {
			Iterator<E> i2 = items.values().iterator();
			while (i2.hasNext()) {
				E o = i2.next();
				buf.ln(o._delta());
			}			
		}
		return buf.toString();
	}

	public String nncsv() throws Exception {
		if (items.size() == 0) return ""; // empty set
		//log.debug("nncsv() first: " + _first() + "to " + this);
		Buffer buf = new Buffer();
		for (Field f : _first().nns()) {
			String cn1 = f.getName();
			if (items.size() > 0) {
				buf.ln(_first()._nnHeader(f));
				Iterator<E> i2 = items.values().iterator();
				while (i2.hasNext()) {
					E o = i2.next();
					BoMap<E> m = (BoMap<E>)o.getBoMap(cn1);
					if (m.size() > 0) {
						buf.add(((Bo)o).getId());
						for (Bo bo : m) {
							// items() of n-n collection, added
							buf.add(Bo.CSVDELIMITER + bo.getId());							
						}
						buf.nl();
					}
				}
			}			
		}
		return buf.toString();
	}

	// This is needed for optimization - incredible increase in performance
	private BoInfo bi;
	protected BoInfo bi() {
		if (bi == null) bi = _first().bi();
		return bi;
	}
	
	protected int writeAllToDB(DBConnection c) throws Exception {
		if (items.size() == 0) return 0; // empty set
		int count = 0;
		String cname = Jugile.getClassName(getClazz());
		// add added and merge modified
		Iterator<E> i2 = items.values().iterator();
		while (i2.hasNext()) {
			E o = i2.next();
			o.setIsNew(true);
			o._dbWriteFlds(c,bi());
			o.setIsNew(false);
			count++;
			if ((count%100) == 0) {
				c.commit();
				log.info("  "+cname+" commit: " + count + " uow: " + dd.d().getCommitSize());
			}
			o.writeAllNNToDB(c,bi()); // n-n data
		}			
		return count;
	}

	
	
	public String toString() {
		String res = "[BoMap " + getClassName(clazz) + " "+ hex(this.hashCode());
		res += " size:"+size();
		if (dd == null) res += " FLD";
		res += "] ";
		return res;
	}

	protected void dump(Buffer buf) throws Exception {
		// Person BoMap: 5430D082
		//   5430D082  Person (2,1,0,-,p1,-)
		//     family: 5430D082

		buf.ln(getClassName(clazz) + " BoMap: " + hex(this.hashCode()));
		buf.incrIndent();
		for (Bo o : this) o.dump(buf);
		buf.decrIndent();
		buf.nl();
	}
	
	protected void dumpShort(Buffer buf) throws Exception {
		//  persons: 5430D082
		//    items: 5430D082, 5430D082
		buf.ind().add("items: ");
		int ind = 0;
		for (Bo o : this) {
			if (ind++ > 0) buf.add(", ");
			buf.add(o.realHash());
		}
		buf.nl();
	}

	protected void stats(Stats st) {
		st.setSize((Class<Bo>)clazz, size());
		for (Bo o : this) {
			o.stats(st,bi());
		}
	}
	
}
